import json
from tkinter import *
from tkinter import filedialog, dialog
import os
import subprocess
import webbrowser
import metadata as md
import globalvar

globalvar._init()

# GUI window
class GUI_window:

    def __init__(self, master=None):
        self.root = master
        self.root.geometry('803x700')
        self.root.title('蓝牙安全测试系统')
        # self.root.bind("<Motion>", self.call_back)
        # 初始化时定义frame
        self.frm_environment = Frame(self.root)
        self.frm_node = Frame(self.root)
        self.frm_metric = Frame(self.root)
        self.frm_output = Frame(self.root)
        self.frm_option = Frame(self.root)
        # 初始化时定义其他界面元素
        self.canvas_topo = Canvas(self.frm_environment)
        self.list_node = Listbox(self.frm_node)
        self.label_metric = Label(self.frm_metric)
        self.label_output = Label(self.frm_output)
        # 根据初始化元素，创建基本界面框架
        
        self.systemini()
        self.demoini()
        self.createpage()

        # self.frm_node.bind('<Button-3>',self.do_job)

    def systemini(self):
        #initailize the system


        pass

    def call_back(self, event):
        print('现在的位置是：', event.x_root, event.y_root)

    def do_job(self,event):
        print("hello world")
        print(event)

    def addnewnode(self):
        print('add new node')
        pass

    def editnode(self,event):
        # print(event)

        pass

    def run_simulation(self):


        pass

    def demoini(self):

        pass

    def shownodemetric(self, event):
        select_flag = FALSE
        w = event.widget
        index = int(w.curselection()[0])
        value = w.get(index)
        bthnodelist = globalvar.get_value('bthnodelist')
        for node in bthnodelist:
            if node.bth_name == value:
                select_node = node
                select_flag = TRUE
        print('get select node')
        if select_flag:
            node_metric = StringVar()
            node_metric.set('节点ID: ' + select_node.bth_id + '\n'
                            + '蓝牙SSID: '+ select_node.bth_ssid + '\n'
                            + '蓝牙MAC: '+ select_node.bth_mac + '\n'
                            + '工作类型: '+ select_node.bth_type + '\n'
                            + '节点名称: '+ select_node.bth_id + '\n')
        else:
            print('Error selection')
            node_metric = StringVar()
            node_metric.set('选择错误！'+ '\n' + '请重新选择!')

        Label(self.frm_metric, fg='black', textvariable=node_metric,height=15,width=20,anchor='nw',
                                                wraplength=120,justify='left',
                                                font='Verdana 8').place(x=3, y=20)

        pass

    def drawtopo(self, nodelist):
        print(nodelist)
        titleFont = ('微软雅黑', 8, 'bold')
        slave_node_op = (2, 'blue', 'blue', None)
        master_node_op = (2, 'green', 'green', None)
        attack_node_op = (2, 'red', 'red', None)
        line_op = (1, 'black', None, None, None)
        slaveplace = []
        masterplace = []
        attackplace = []
        global bthnodelist_salve, bthnodelist_master, bthnodelist_attacker, envrionment_topo
        for si in bthnodelist_salve:
            yplaceid = si.bth_id.split('_')[1]
            nodeimage_xplace = 400
            nodetext_xplace = nodeimage_xplace - 10
            nodeimage_yplace = (int(yplaceid) * 50)
            nodetext_yplace = nodeimage_yplace - 10
            self.canvas_topo.create_oval(nodeimage_xplace, nodeimage_yplace, nodeimage_xplace+20, nodeimage_yplace+20,
                width=slave_node_op[0],  # 边框宽度
                fill=slave_node_op[1],  # 填充颜色
                outline=slave_node_op[2],  # 边框颜色
                stipple=slave_node_op[3])  # 使用位图填充
            self.canvas_topo.create_text(nodetext_xplace, nodetext_yplace, text = si.bth_id,
                font = titleFont,
                fill='blue',
                anchor = W,
                justify = LEFT)
            place = [nodeimage_xplace,nodeimage_yplace]
            slaveplace.append(place)
            
            pass

        for ci in bthnodelist_master:
            nodeimage_xplace = 40
            nodetext_xplace = nodeimage_xplace - 10
            yplaceid  = ci.bth_id.split('_')[1]
            nodeimage_yplace = 50 + (int(yplaceid)  * 300)
            nodetext_yplace = nodeimage_yplace - 10
            self.canvas_topo.create_rectangle(nodeimage_xplace, nodeimage_yplace, nodeimage_xplace+20, nodeimage_yplace+20,
                    width=master_node_op[0],  # 边框宽度
                    fill=master_node_op[1],  # 填充颜色
                    outline=master_node_op[2],  # 边框颜色
                    stipple=master_node_op[3])  # 使用位图填充
            self.canvas_topo.create_text(nodetext_xplace, nodetext_yplace, text = ci.bth_id,
                    font = titleFont,
                    fill='green',
                    anchor = W,
                    justify = LEFT)
            place = [nodeimage_xplace,nodeimage_yplace]
            masterplace.append(place)
      
            pass
        
        for ai in bthnodelist_attacker:
            yplaceid  = ai.bth_id.split('_')[1]
            nodeimage_xplace = 40
            nodetext_xplace = nodeimage_xplace - 10
            nodeimage_yplace = 50 + (int(yplaceid)  * 50)
            nodetext_yplace = nodeimage_yplace - 30
            self.canvas_topo.create_polygon(nodeimage_xplace, nodeimage_yplace, nodeimage_xplace+10, nodeimage_yplace-20, nodeimage_xplace+20, nodeimage_yplace,
            width=attack_node_op[0],  # 边框宽度
            fill=attack_node_op[1],  # 填充颜色
            outline=attack_node_op[2],  # 边框颜色
            stipple=attack_node_op[3])  # 使用位图填充
            self.canvas_topo.create_text(nodetext_xplace,nodetext_yplace, text = ai.bth_id,
                    font = titleFont,
                    fill='red',
                    anchor = W,
                    justify = LEFT)
            place = [nodeimage_xplace,nodeimage_yplace]
            attackplace.append(place)

            pass


        # print(envrionment_topo.linkmatrix[0][0])
        # for si in range(envrionment_topo.servernum):
        #     sx = slaveplace[si-1][0]
        #     sy = slaveplace[si-1][1]
        #     for ci in range(envrionment_topo.usernum):
        #         if envrionment_topo.linkmatrix[si-1][envrionment_topo.servernum+ci-1] > 0:
        #             cx = masterplace[ci-1][0]
        #             cy = masterplace[ci-1][1]
        #             self.canvas_topo.create_line(cx+10, cy+10, sx+10, sy+10,
        #             width = line_op[0], # 边框宽度
        #             fill = line_op[1], # 填充颜色
        #             stipple = line_op[2], # 使用位图填充
        #             arrow = line_op[3], # 箭头风格
        #             arrowshape = line_op[4]) # 箭头形状
        #             pass
        #         pass
            
        #     for ai in range(envrionment_topo.attackernum):
        #         if envrionment_topo.linkmatrix[si-1][envrionment_topo.servernum+envrionment_topo.usernum+ai-1] > 0:
        #             ax = attackplace[ai-1][0]
        #             ay = attackplace[ai-1][1]
        #             self.canvas_topo.create_line(ax+10, ay-10, sx+10, sy+10,
        #             width = line_op[0], # 边框宽度
        #             fill = line_op[1], # 填充颜色
        #             stipple = line_op[2], # 使用位图填充
        #             arrow = line_op[3], # 箭头风格
        #             arrowshape = line_op[4]) # 箭头形状
        #             pass
        #         pass

        #     pass
    
        
        pass

    def bindnodes(self):
        print("bind nodes to topo")
        self.canvas_topo.place(x=50, y=50, width=500, height=400)
        listi = 0
        node_list_get = []
        while self.list_node.get(listi) != '':
            # print('{0} 和 {1}'.format(listi, self.list_node.get(listi)))
            node_list_get.append(self.list_node.get(listi))
            listi = listi + 1
            pass
        # print("%s", node_list_get)
        self.drawtopo(node_list_get)

        var_nowtopo = StringVar()  # Label显示的文字要是会变化的话，只接受这种类型的变量
        var_nowtopo.set("模拟实验环境扫描已加载！")
        Label(self.frm_environment, fg='black', textvariable=var_nowtopo, 
                                                font='Verdana 10').place(x=200, y=0, height=30, width=200)
        pass


    def insert_demo(self):
        global bthnodelist_salve, bthnodelist_master, bthnodelist_attacker, envrionment_topo
        bthnodelist = []
        bthnodelist_salve = []
        bthnodelist_master = []
        bthnodelist_attacker = []
        # Bthattribure = md.Bthattribute
        bthnode_slave = md.Bthnode('slave_1','Temperature-humidity-slave','BTH-Sensor','08-00-20-0A-8C-6D','slave')
        bthnode_master = md.Bthnode('master_1','Temperature-humidity-master','BTH-Sever','08-00-20-0A-8C-6C','master')
        bthnode_attacker = md.Bthnode('attack_1','Temperature-humidity-attacker','BTH-Attacker','10-F0-05-31-01-DC','attacker')
        bthnodelist_salve.append(bthnode_slave)
        bthnodelist_master.append(bthnode_master)
        bthnodelist_attacker.append(bthnode_attacker)
        bthnodelist = bthnodelist_salve + bthnodelist_master + bthnodelist_attacker
        globalvar.set_value('bthnodelist',bthnodelist)
        # bthnodelist_json = json.dumps(bthnodelist, ensure_ascii=False)
        simresults = StringVar()
        simresults.set('节点生成完毕!\n 拓扑生成完毕!\n 模拟环境参数设置完毕!\n\n 可以进行蓝牙安全测试模拟!\n')
        self.label_output.config(textvariable=simresults)
        self.label_output.place(x=0, y=20)
        
        pass

    def run_bthscan(self):
        global bthnodelist_salve, bthnodelist_master, bthnodelist_attacker, envrionment_topo
        var_demo = StringVar()  # 定义变量
        var_demo_nodenamelist = []
        for si in bthnodelist_salve:
            var_demo_nodenamelist.append(si.bth_name)
            pass
        for mi in bthnodelist_master:
            var_demo_nodenamelist.append(mi.bth_name)
            pass
        for ai in bthnodelist_attacker:
            var_demo_nodenamelist.append(ai.bth_name)
            pass
        var_demo.set(var_demo_nodenamelist)
        # scrolly = Scrollbar(self.frm_node)
        # scrolly.pack(side=RIGHT, fill=Y)
        self.list_node.config(selectmode=SINGLE, listvariable=var_demo)
        self.list_node.place(x=3, y=30, width=140)
        # scrolly.config(command=node_list.yview)
        self.list_node.bind('<Double-Button-1>', self.shownodemetric)
        # 添加list的menu菜单项
  
        self.listmenu = Menu(self.root,tearoff=0)
        self.listmenu.add_command(label="分析", command=self.run_bthanalysis)
        # self.listmenu.add_command(label="修改",command=self.editnode)
        # self.listmenu.add_command(label="删除",command=self.do_job)
        self.list_node.bind('<Button-3>',self.popmenu)

        self.bindnodes()

        scan_results = StringVar()
        # cmdstr = 'ubertooth-btle -f -t 74:e1:82:04:53:3f -c /tmp/pipe'
        cmdstr = 'sudo bluetoothctl'
        scan_results.set(cmdstr + '\n' + '蓝牙节点扫描完毕!\n 可以进行蓝牙数据分析模拟!\n')
        self.label_output.config(textvariable=scan_results)
        self.label_output.place(x=0, y=20)

        # todo
        # 扫描结束后，绘制master到slave的连线

        pass

    def run_bthanalysis(self):
        global bthnodelist_salve, bthnodelist_master, bthnodelist_attacker, envrionment_topo
        maclist = []
        for node in bthnodelist_salve:
            maclist.append(node.bth_mac)
        cmdstrlist = []
        cmdstr_pre = 'ubertooth-btle -f -t '
        cmdstr_last = ' -c /tmp/pipe \n'
        for mac in maclist:
            cmdstr = cmdstr_pre + mac + cmdstr_last
            cmdstrlist.append(cmdstr)

        analysis_results = StringVar()
        
        analysis_results.set(''.join(cmdstrlist) + '蓝牙数据捕获完毕!\n 可以进行查看数据包信息!\n')
        self.label_output.config(textvariable=analysis_results)
        self.label_output.place(x=0, y=20)
        pass

    def runbthwireshark(self):
        # todo
        # kali 里实现调用wireshark
        cmd="wireshark"
        os.system(cmd)
        wireshark_results = StringVar()
        wireshark_results.set('打开wireshark查看数据包!\n 可以进行密钥分析模拟!\n')
        self.label_output.config(textvariable=wireshark_results)
        self.label_output.place(x=0, y=20)
        pass


    def run_bthcrackle(self):
        # 使用askopenfilename函数选择单个文件
        crackle_file_selected_file_path = filedialog.askopenfilename(title='选择需要破解的*.pacp文件')
        filetype = crackle_file_selected_file_path.split('.')[-1]
        if filetype !='pcap':
            dialog.Dialog(None, {'title': 'File Type Error', 'text': '打开文件类型错误', 'bitmap': 'error', 'default': 0,
                                    'strings': ('OK', 'Cancle')})
            pass
        else:
            crackle_str = 'crackle -i ' + crackle_file_selected_file_path + '\n'
            crackle_information = '进行密钥分析!\n 密钥分析中……\n 密钥分析中……\n 密钥分析中……\n'
            crackle_results = StringVar()
            crackle_results.set(crackle_str + crackle_information)
            self.label_output.config(textvariable=crackle_results)
            self.label_output.place(x=0, y=20)
            # time.sleep( 3 )
            #todo
            #延迟输出
            crackle_key_information = '密钥分析完毕！\n 获取密钥为：'
            crackle_key = '7f62c053f104a5bbe68b1d896a2ed49c \n'
            crackle_results.set(crackle_key_information + crackle_key)
            self.label_output.config(textvariable=crackle_results)
            self.label_output.place(x=0, y=20)
        pass


    def Bth_replay(self):
        crackle_str = "gatttool"+" -b"+" 74:e1:82:04:53:3f"+" -I\n"#mac地址以实际地址为准
        crackle_information = '[connect]--连接设备\n进行第一步重放\n 进行第二步重放\n 进行第三步重放\n'#具体重放什么数据根据抓包蓝牙连接时内容确定
        crackle_results = StringVar()
        crackle_results.set(crackle_str + crackle_information)
        self.label_output.config(textvariable=crackle_results)
        self.label_output.place(x=0, y=20)
        # time.sleep( 3 )
        #todo
        #延迟输出
        crackle_key_information = '重放完毕！正在监听数据\n '
        crackle_key = '01 03 04 01 FE OO FA 1A 7C \n01 03 04 01 FE OO FA 1A 7C \n01 03 04 01 FE OO FA 1A 7C \n'
        crackle_results.set(crackle_key_information + crackle_key)
        self.label_output.config(textvariable=crackle_results)
        self.label_output.place(x=0, y=20)

        # todo
        # 重放时，绘制attacker到slave的连线

        pass

    def popmenu(self,event):
        print(event)
        w = event.widget
        index = int(w.curselection()[0])
        value = w.get(index)
        print(value)
        # self.listmenu.post(event.x_root, event.y_root)
        pass

    def openconfigfile(self):
        print('open user config files!')
        filename = filedialog.askopenfilename(title='打开用户定义模拟数据文件')
        print(filename)
        filetype = filename.split('.')[-1]
        if filetype != 'json':
            dialog.Dialog(None, {'title': 'File Type Error', 'text': '打开文件类型错误', 'bitmap': 'error', 'default': 0,
                                    'strings': ('OK', 'Cancle')})
            pass
        else:
            with open(filename,'r') as f:
                # todo
                # 从json文件里读取模拟节点的信息
                data=json.load(f)
                global bthnodelist_salve, bthnodelist_master, bthnodelist_attacker, envrionment_topo
                bthnodelist = []
                bthnodelist_salve = []
                bthnodelist_master = []
                bthnodelist_attacker = []
                # Bthattribure = md.Bthattribute
                #"9C-A5-25-C5-F5-D8",
                #"9C-A5-25-C5-F4-F4",
                for i in range(len(data)):
                    tmp=data[i]
                    if 'slave' in tmp['bth_id']:
                        bthnode_slave = md.Bthnode(tmp['bth_id'], tmp['bth_ssid'], tmp['bth_name'], tmp['bth_mac'],
                                           tmp['bth_id'])
                    elif 'master' in tmp['bth_id']:
                        bthnode_master = md.Bthnode(tmp['bth_id'], tmp['bth_ssid'], tmp['bth_name'], tmp['bth_mac'],
                                           tmp['bth_id'])
                    elif 'attack' in tmp['bth_id']:
                        bthnode_attacker = md.Bthnode(tmp['bth_id'], tmp['bth_ssid'], tmp['bth_name'], tmp['bth_mac'],
                                           tmp['bth_id'])
                bthnodelist_salve.append(bthnode_slave)
                bthnodelist_master.append(bthnode_master)
                bthnodelist_attacker.append(bthnode_attacker)
                bthnodelist = bthnodelist_salve + bthnodelist_master + bthnodelist_attacker
                globalvar.set_value('bthnodelist', bthnodelist)
                var_demo = StringVar()  # 定义变量
                var_demo_nodenamelist = []
                for si in bthnodelist_salve:
                    var_demo_nodenamelist.append(si.bth_name)
                    pass
                for mi in bthnodelist_master:
                    var_demo_nodenamelist.append(mi.bth_name)
                    pass
                for ai in bthnodelist_attacker:
                    var_demo_nodenamelist.append(ai.bth_name)
                    pass
                var_demo.set(var_demo_nodenamelist)
                # scrolly = Scrollbar(self.frm_node)
                # scrolly.pack(side=RIGHT, fill=Y)
                self.list_node.config(selectmode=SINGLE, listvariable=var_demo)
                self.list_node.place(x=3, y=30, width=140)
                # scrolly.config(command=node_list.yview)
                self.list_node.bind('<Double-Button-1>', self.shownodemetric)
                # 添加list的menu菜单项

                self.bindnodes()

                scan_results = StringVar()
                # cmdstr = 'ubertooth-btle -f -t 74:e1:82:04:53:3f -c /tmp/pipe'
                scan_results.set(cmdstr + '\n' + '文件打开成功!\n ')
                self.label_output.config(textvariable=scan_results)
                self.label_output.place(x=0, y=20)
                pass
            if f.closed:
                print('User Config file read!')
            pass

    def saveconfigfile(self):
        bthnodelist = globalvar.get_value('bthnodelist')
        bthnodelist_dict = []
        # print(bthnodelist)
        if bthnodelist:
            for node in bthnodelist:
                bthnodelist_dict.append(node.asdict())
                pass
            file_path = filedialog.asksaveasfilename(title='保存文件')
            print('保存文件：', file_path)
            if file_path is not None:
                with open(file=file_path, mode='w', encoding='utf-8') as file:
                    dialog.Dialog(None, {'title': 'File Saved', 'text': '保存完成', 'bitmap': 'warning', 'default': 0,
                                            'strings': ('OK', 'Cancle')})
                    json.dump(bthnodelist_dict, file, indent=4, ensure_ascii=False)
                    print('保存完成')
                    file.close()
                pass
        else:
            dialog.Dialog(None, {'title': 'File Type Error', 'text': '无可保存的模拟文件', 'bitmap': 'error', 'default': 0,
                                    'strings': ('OK', 'Cancle')})
            pass
        pass

        
    def gethelps(self):
        dialog.Dialog(None, {'title': 'Get Helps', 'text': '蓝牙安全测试系统V1.0', 'bitmap': 'question', 'default': 0,
                            'strings': ('OK', 'Cancle')})
        webbrowser.open("https://gitee.com/xiong-xinli/bluetooth-security-test", new=0)
        print('获取帮助')
        pass


    def createpage(self):
        # 添加menu菜单项
        menu = Menu(self.root)
        self.root.config(menu=menu)
        # 文件菜单初始化
        filemenu = Menu(menu, tearoff=0)
        menu.add_cascade(label='文件', menu=filemenu)
        filemenu.add_command(label='新建', command=self.insert_demo)
        filemenu.add_command(label='打开', command=self.openconfigfile)
        filemenu.add_command(label='保存', command=self.saveconfigfile)
        filemenu.add_separator()    # 添加一条分隔线
        # 用tkinter里面自带的quit()函数
        filemenu.add_command(label='退出', command=self.root.quit)    
       
        # 运行菜单初始化
        runmenu = Menu(menu, tearoff=0)
        menu.add_cascade(label='扫描', menu=runmenu)
        runmenu.add_command(label='模拟扫描', command=self.run_bthscan)
        runmenu.add_command(label='扫描', command=self.run_simulation)
        # runmenu.add_command(label='结果分析', command=self.do_job)
        
        #分析菜单初始化
        runmenu = Menu(menu, tearoff=0)
        menu.add_cascade(label='分析', menu=runmenu)
        runmenu.add_command(label='数据抓取', command=self.run_bthanalysis)
        runmenu.add_command(label='启动wireshark',command=self.runbthwireshark)
        runmenu.add_command(label='密钥分析', command=self.run_bthcrackle)

        #重放菜单初始化
        runmenu = Menu(menu, tearoff=0)
        menu.add_cascade(label='重放', menu=runmenu)
        runmenu.add_command(label='模拟重放', command=self.Bth_replay)
        runmenu.add_command(label='蓝牙重放', command=self.do_job)

        # 帮助菜单初始化
        helpmenu = Menu(menu, tearoff=0)
        menu.add_cascade(label='帮助',menu=helpmenu)
        helpmenu.add_command(label='查看帮助', command=self.gethelps)
        
        # 生成对frame的配置（位置，大小，颜色等等）
        # self.frm_option.config(bg='grey', height=30, width=760)
        # Label(self.frm_option, text='Option').place(
        #     in_=self.frm_option, anchor=NW)
        # self.frm_option.place(x=20, y=0)

        self.frm_environment.config(height=500, width=600,bd=2,relief=GROOVE) # bg='blue', 
        Label(self.frm_environment, text='实验环境').place(in_=self.frm_environment, anchor=NW)
        self.frm_environment.place(x=180, y=0)

        self.frm_node.config( height=250, width=150,bd=2,relief=GROOVE) # bg='red',
        Label(self.frm_node, text='蓝牙节点信息').place(anchor=NW)
        self.frm_node.place(x=20, y=0)

        self.frm_metric.config( height=250, width=150,bd=2,relief=GROOVE) # bg='yellow',
        Label(self.frm_metric, text='蓝牙节点属性').place(
            in_=self.frm_metric, anchor=NW)
        self.frm_metric.place(x=20, y=250)

        self.frm_output.config(height=150, width=760,bd=2,relief=GROOVE) # bg='green', 
        Label(self.frm_output, text='执行输出').place(
            in_=self.frm_output, anchor=NW)
        self.frm_output.place(x=20, y=510)

        # frm_node下listbox
        # scrolly = Scrollbar(self.frm_node)
        #  # scrolly.pack(side=RIGHT, fill=Y)
        # Listbox(self.frm_node,yscrollcommand=scrolly.set).place(x=5, y=30, width=140)
        # scrolly.config(command=node_list.yview)

        # frm_metric下的Label
        # Label(self.frm_metric, text='Metric Show:',
        #       fg='red', font='Verdana 10 bold').place(x=0, y=30)

        # Label(self.frm_output,text='Result Show',fg='red', font='Verdana 10 bold').place(x=0,y=20)
        # frm_option下的Button
        # Button(self.frm_option, text='Insert',
        #        command=self.insert_demo).place(x=50, y=0, width=50)
        # Button(self.frm_option, text='Bind',
        #        command=self.bindnodes).place(x=110, y=0, width=50)
        # Button(self.frm_option, text='Start').place(x=170, y=0, width=50)
        # Button(self.frm_option, text='Stop').place(x=230, y=0, width=50)
        # Button(self.frm_option, text='Clear ').place(x=290, y=0, width=50)

        # frm_topo下的控件
        # Label(self.frm_topo, text='拓扑结构展示',
        #       fg='red', font='Verdana 10 bold').place(x=100, y=50, height=80, width=400)


if __name__ == '__main__':
    # global config
    # with open('config.ini', 'r', encoding='utf-8') as f:
    #     configfilestr = f.read().strip()
    #     # print(configfilestr)
    #     configlist = configfilestr.splitlines()
    # if f.closed:
    #     print('Config file config.ini read over!')
    #     pass
    # else:
    #     print('Config Error!')
    #     pass


    root = Tk()
    GUI_window(root)
    mainloop()
